// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/synchronization/read_write_lock.h"

#include <stdlib.h>

#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace subtle {

    // Basic test to make sure that *Acquire()/*Release() don't crash.

    class BasicReadWriteLockTestThread : public PlatformThread::Delegate {
    public:
        explicit BasicReadWriteLockTestThread(ReadWriteLock* lock)
            : lock_(lock)
            , acquired_(0)
        {
        }

        void ThreadMain() override
        {
            for (int i = 0; i < 10; i++) {
                AutoReadLock locker(*lock_);
                acquired_++;
            }
            for (int i = 0; i < 10; i++) {
                AutoWriteLock locker(*lock_);
                acquired_++;
                PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20));
            }
        }

        int acquired() const { return acquired_; }

    private:
        ReadWriteLock* lock_;
        int acquired_;

        DISALLOW_COPY_AND_ASSIGN(BasicReadWriteLockTestThread);
    };

    TEST(ReadWriteLockTest, Basic)
    {
        ReadWriteLock lock;
        BasicReadWriteLockTestThread thread(&lock);
        PlatformThreadHandle handle;

        ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));

        int acquired = 0;
        for (int i = 0; i < 5; i++) {
            AutoReadLock locker(lock);
            acquired++;
        }
        for (int i = 0; i < 10; i++) {
            AutoWriteLock locker(lock);
            acquired++;
            PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20));
        }
        for (int i = 0; i < 5; i++) {
            AutoReadLock locker(lock);
            acquired++;
        }

        PlatformThread::Join(handle);

        EXPECT_EQ(20, acquired);
        EXPECT_GE(20, thread.acquired());
    }

    // Tests that reader locks allow multiple simultaneous reader acquisitions.

    class ReaderReadWriteLockTestThread : public PlatformThread::Delegate {
    public:
        ReaderReadWriteLockTestThread(ReadWriteLock* lock)
            : lock_(lock)
        {
        }

        void ThreadMain() override
        {
            AutoReadLock locker(*lock_);
            did_acquire_ = true;
        }

        bool did_acquire() const { return did_acquire_; }

    private:
        ReadWriteLock* lock_;
        bool did_acquire_ = false;

        DISALLOW_COPY_AND_ASSIGN(ReaderReadWriteLockTestThread);
    };

    TEST(ReadWriteLockTest, ReaderTwoThreads)
    {
        ReadWriteLock lock;

        AutoReadLock auto_lock(lock);

        ReaderReadWriteLockTestThread thread(&lock);
        PlatformThreadHandle handle;

        ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
        PlatformThread::Join(handle);
        EXPECT_TRUE(thread.did_acquire());
    }

    // Tests that writer locks exclude reader locks.

    class ReadAndWriteReadWriteLockTestThread : public PlatformThread::Delegate {
    public:
        ReadAndWriteReadWriteLockTestThread(ReadWriteLock* lock, int* value)
            : lock_(lock)
            , value_(value)
            , event_(WaitableEvent::ResetPolicy::MANUAL,
                  WaitableEvent::InitialState::NOT_SIGNALED)
        {
        }

        void ThreadMain() override
        {
            AutoWriteLock locker(*lock_);
            (*value_)++;
            event_.Signal();
        }

        void Wait()
        {
            event_.Wait();
        }

    private:
        ReadWriteLock* lock_;
        int* value_;
        WaitableEvent event_;

        DISALLOW_COPY_AND_ASSIGN(ReadAndWriteReadWriteLockTestThread);
    };

    TEST(ReadWriteLockTest, ReadAndWriteThreads)
    {
        ReadWriteLock lock;
        int value = 0;

        ReadAndWriteReadWriteLockTestThread thread(&lock, &value);
        PlatformThreadHandle handle;
        {
            AutoReadLock read_locker(lock);
            ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));

            PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));

            // |value| should be unchanged since we hold a reader lock.
            EXPECT_EQ(0, value);
        }

        thread.Wait();
        // After releasing our reader lock, the thread can acquire a write lock and
        // change |value|.
        EXPECT_EQ(1, value);
        PlatformThread::Join(handle);
    }

    // Tests that writer locks actually exclude.

    class WriterReadWriteLockTestThread : public PlatformThread::Delegate {
    public:
        WriterReadWriteLockTestThread(ReadWriteLock* lock, int* value)
            : lock_(lock)
            , value_(value)
        {
        }

        // Static helper which can also be called from the main thread.
        static void DoStuff(ReadWriteLock* lock, int* value)
        {
            for (int i = 0; i < 40; i++) {
                AutoWriteLock locker(*lock);
                int v = *value;
                PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 10));
                *value = v + 1;
            }
        }

        void ThreadMain() override { DoStuff(lock_, value_); }

    private:
        ReadWriteLock* lock_;
        int* value_;

        DISALLOW_COPY_AND_ASSIGN(WriterReadWriteLockTestThread);
    };

    TEST(ReadWriteLockTest, MutexTwoThreads)
    {
        ReadWriteLock lock;
        int value = 0;

        WriterReadWriteLockTestThread thread(&lock, &value);
        PlatformThreadHandle handle;

        ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));

        WriterReadWriteLockTestThread::DoStuff(&lock, &value);

        PlatformThread::Join(handle);

        EXPECT_EQ(2 * 40, value);
    }

    TEST(ReadWriteLockTest, MutexFourThreads)
    {
        ReadWriteLock lock;
        int value = 0;

        WriterReadWriteLockTestThread thread1(&lock, &value);
        WriterReadWriteLockTestThread thread2(&lock, &value);
        WriterReadWriteLockTestThread thread3(&lock, &value);
        PlatformThreadHandle handle1;
        PlatformThreadHandle handle2;
        PlatformThreadHandle handle3;

        ASSERT_TRUE(PlatformThread::Create(0, &thread1, &handle1));
        ASSERT_TRUE(PlatformThread::Create(0, &thread2, &handle2));
        ASSERT_TRUE(PlatformThread::Create(0, &thread3, &handle3));

        WriterReadWriteLockTestThread::DoStuff(&lock, &value);

        PlatformThread::Join(handle1);
        PlatformThread::Join(handle2);
        PlatformThread::Join(handle3);

        EXPECT_EQ(4 * 40, value);
    }

} // namespace subtle
} // namespace base
